﻿using JavaVirtualMachine.ConstantPoolItems;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace JavaVirtualMachine
{
    public static class JavaHelper
    {
        public static int CurrentBaseOffset;

        public static int CurrentReturnValue;

        public static long CurrentReturnLargeValue;
        public static int NumOfArgs(this MethodInfo method) => NumOfArgs(method.Descriptor);
        public static int NumOfExplicitArgs(this CMethodRefInfo methodRef) => NumOfArgs(methodRef.Descriptor);
        public static int NumOfExplicitArgs(this CInterfaceMethodRefInfo interfaceMethodRef) => NumOfArgs(interfaceMethodRef.Descriptor);
        public static int NumOfArgs(string descriptor)
        {
            int numOfArgs = 0;
            int i;
            for (i = 1; descriptor[i] != ')';)
            {
                if (descriptor[i] == 'J' || descriptor[i] == 'D')
                {
                    numOfArgs += 2;
                    i++;
                }
                else
                {
                    numOfArgs++;

                    //Move to next arg
                    if (descriptor[i] == '[')
                    {
                        i++;
                        if (descriptor[i] == '[')
                        {
                            i++;
                            if (descriptor[i] == '[')
                            {
                                i++;
                                if (descriptor[i] == '[')
                                {
                                    i++;
                                }
                            }
                        }
                    }
                    if (descriptor[i] == 'L')
                    {
                        for (i++; descriptor[i] != ';'; i++) { }
                    }
                    i++;
                }
            }
            return numOfArgs;
        }

        public static List<string> GetArgsType(string descriptor)
        {
            int numOfArgs = 0;
            int i;
            List<string> types = new List<string>();
            for (i = 1; descriptor[i] != ')';)
            {
                string str = "";
                if (descriptor[i] == 'J' || descriptor[i] == 'D')
                {
                    numOfArgs += 2;
                    types.Add(descriptor[i].ToString());
                    i++;
                }
                else
                {
                    numOfArgs++;

                    //Move to next arg
                    if (descriptor[i] == '[')
                    {
                        i++;
                        str += "[";
                        if (descriptor[i] == '[')
                        {
                            i++;
                            str += "[";

                            if (descriptor[i] == '[')
                            {
                                i++;
                                str += "[";

                                if (descriptor[i] == '[')
                                {
                                    i++;
                                }
                            }
                        }
                    }
                    if (descriptor[i] == 'L')
                    {
                        for (i++; descriptor[i] != ';'; i++)
                        {
                            str += descriptor[i];
                        }
                    }
                    else
                    {
                        str += descriptor[i];
                    }
                    i++;
                    types.Add(str);
                }
            }
            
            return types;
        }
        public static int[] GetArgsTypeToAddr(string descriptor)
        {
            List<string> types = GetArgsType(descriptor);
            int[] result=new int[types.Count];
            for (int j= 0; j < types.Count; j++)
            {
                result[j] = ClassObjectManager.GetClassObjectAddr(types[j]);
            }
            return result;
        }
        public static int CreateJavaStringLiteral(string str,bool isUTF8=false)
        {
            Encoding encoding = Encoding.Unicode;
            //if (str.Contains("加载"))
            //{
            //    encoding= Encoding.ASCII;
            //}

            if (StringPool.StringAddresses.TryGetValue(str, out int strAddr))
            {
                return strAddr;
            }
            
            if (isUTF8)
                encoding = Encoding.UTF8;
            byte[] data = encoding.GetBytes(str);
            int charArrAddr = Heap.CreateArray(2, (int)Math.Ceiling( data.Length / 2f), ClassObjectManager.GetClassObjectAddr("char"));
            //int charArrAddr = Heap.CreateArray(2, (int)( data.Length / 2f), ClassObjectManager.GetClassObjectAddr("char"));
            data.CopyTo(Heap.GetArray(charArrAddr).GetDataSpan());

            int newStrObjAddr = Heap.CreateObject(ClassFileManager.GetClassFileIndex("java/lang/String"));
            Heap.GetObject(newStrObjAddr).SetField("value", "[C", charArrAddr);

            StringPool.StringAddresses.Add(str, newStrObjAddr);
            return newStrObjAddr;
        }

        public static string ReadJavaString(int address,bool isUTF8 = false)
        {
            HeapObject stringObj = Heap.GetObject(address);
            int charArrAddr = stringObj.GetField("value", "[C");
            HeapArray charArray = Heap.GetArray(charArrAddr);
            ReadOnlySpan<byte> array = charArray.GetDataSpan();
            //return Encoding.UTF8.GetString(bytes).TrimEnd('\0');
            //if (isUTF8)
            {
                //Span<byte> array = charArray.GetDataSpan();
                List<byte> newArray = new List<byte>(array.Length);

                for (int i = 0; i < array.Length; i++)
                {
                    if (array[i] != 0)
                        newArray.Add(array[i]);
                }
                if (newArray.Count == array.Length / 2)
                    return Encoding.UTF8.GetString(newArray.ToArray());
                return Encoding.Unicode.GetString(array);

            }
            return Encoding.Unicode.GetString(array);
        }

        public static void ReturnValue(Stack<MethodFrame> MethodFrameStack, Memory<int> GlobalStack,int retVal)
        {
            MethodFrame currFrame = MethodFrameStack.Peek();
            MethodInfo currMethod = currFrame.Method;
            Program.StackTracePrinter.PrintMethodReturn(currMethod, retVal);
            GlobalStack.Span[currFrame.BaseOffset] = retVal;
            CurrentReturnValue = retVal;
            CurrentBaseOffset = currFrame.BaseOffset;
        }

        public static void ReturnLargeValue(Stack<MethodFrame> MethodFrameStack, Memory<int> GlobalStack,long retVal)
        {
            MethodFrame currFrame = MethodFrameStack.Peek();
            MethodInfo currMethod = currFrame.Method;
            Program.StackTracePrinter.PrintMethodReturn(currMethod, retVal);
            (int high, int low) = Utility.Split(retVal);
            CurrentReturnLargeValue = retVal;
            GlobalStack.Span[currFrame.BaseOffset] = high;
            GlobalStack.Span[currFrame.BaseOffset + 1] = low;
            CurrentBaseOffset = currFrame.BaseOffset;
        }

        public static (int,int) GetReturnLargeValue(Stack<MethodFrame> MethodFrameStack, Memory<int> GlobalStack)
        {
            return (GlobalStack.Span[CurrentBaseOffset], GlobalStack.Span[CurrentBaseOffset+1]);
        }

        public static int GetReturnValue(Stack<MethodFrame> MethodFrameStack, Memory<int> GlobalStack)
        {
            return GlobalStack.Span[CurrentBaseOffset];
        }

        public static void ReturnVoid(Stack<MethodFrame> MethodFrameStack, Memory<int> GlobalStack)
        {
            MethodFrame currFrame = MethodFrameStack.Peek();
            MethodInfo currMethod = currFrame.Method;
            Program.StackTracePrinter.PrintMethodReturn(currMethod);
        }

        public static bool IsArray(this CClassInfo classInfo)
        {
            return classInfo.Name[0] == '[';
        }

        public static bool IsArray(this string className)
        {
            return className[0] == '[';
        }

        public static float StoredFloatToFloat(int storedFloat)
        {
            float asFloat;
            unsafe
            {
                int* storedFloatPtr = &storedFloat;
                float* asFloatPtr = (float*)storedFloatPtr;
                asFloat = *asFloatPtr;
            }
            return asFloat;
        }

        public static int FloatToStoredFloat(float floatValue)
        {
            int storedFloat;
            unsafe
            {
                float* floatValPtr = &floatValue;
                int* storedFloatPtr = (int*)floatValPtr;
                storedFloat = *storedFloatPtr;
            }
            return storedFloat;
        }

        public static double StoredDoubleToDouble(long storedDouble)
        {
            double asDouble;
            unsafe
            {
                long* storedDoublePtr = &storedDouble;
                double* asDoublePtr = (double*)storedDoublePtr;
                asDouble = *asDoublePtr;
            }
            return asDouble;
        }

        public static long DoubleToStoredDouble(double doubleValue)
        {
            long storedDouble;
            unsafe
            {
                double* doubleValPtr = &doubleValue;
                long* storedDoublePtr = (long*)doubleValPtr;
                storedDouble = *storedDoublePtr;
            }
            return storedDouble;
        }

        public static bool ImplementsInterface(this ClassFile classFileToCheck, ClassFile interfaceToCheck)
        {
            if (classFileToCheck == interfaceToCheck) return true;

            Stack<ClassFile> supers = new Stack<ClassFile>(); //Super-classes and interfaces and super-interfaces
            supers.Push(classFileToCheck);

            while (supers.Count > 0)
            {
                ClassFile classFile = supers.Pop();

                //Don't need to check classFile == interfaceToCheck because everything is checked before being pushed to the stack

                if (classFile.SuperClass != null)
                {
                    if (classFile.SuperClass == interfaceToCheck) return true;
                    supers.Push(classFile.SuperClass);
                }

                //Check the names of the interfaces first so that no interfaces are loaded if one of them is the interface to check
                foreach (string interfaceName in classFile.InterfaceNames)
                {
                    if (interfaceName == interfaceToCheck.Name) return true;
                }
                foreach (string interfaceName in classFile.InterfaceNames)
                {
                    supers.Push(ClassFileManager.GetClassFile(interfaceName));
                }
            }
            return false;
        }

        internal static void ReturnValue(Stack<MethodFrame> methodFrameStack, Memory<int> globalStack, object uID)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Returns if classFile is the same as or is a subclass of potentialSuperClass
        /// </summary>
        public static bool IsSubClassOf(this ClassFile classFile, ClassFile potentialSuperClass)
        {
            ClassFile temp = classFile;
            while (temp != null)
            {
                if (temp == potentialSuperClass) return true;
                temp = temp.SuperClass;
            }
            return false;
        }

        public static string PrimitiveFullName(string name)
        {
            switch (name)
            {
                case "Z":
                    return "boolean";
                case "B":
                    return "byte";
                case "C":
                    return "char";
                case "D":
                    return "double";
                case "F":
                    return "float";
                case "I":
                    return "int";
                case "J":
                    return "long";
                case "S":
                    return "short";
                case "V":
                    return "void";
                default:
                    if (ClassFileManager.TryGetValue(name))
                        return name;
                    string[] className = name.Split('$');
                    if (className.Length>1)
                    {
                        if (ClassFileManager.TryGetValue(className[0]))
                            return name;
                    }
                    throw new ArgumentException("Unrecognized primitive name", name);
            }
        }

        public static bool IsPrimitiveType(string type)
        {
            return type == "boolean" ||
                    type == "byte" ||
                    type == "char" ||
                    type == "double" ||
                    type == "float" ||
                    type == "int" ||
                    type == "long" ||
                    type == "short" ||
                    type == "void";
        }

        public static string ClassObjectName(int classObjAddr)
        {
            return ClassObjectName(Heap.GetObject(classObjAddr));
        }

        public static string ClassObjectName(HeapObject classObj)
        {
            return ReadJavaString(classObj.GetField("name", "Ljava/lang/String;"));
        }

        public static string ReadDescriptorArg(string descriptor, ref int i)
        {
            int startI = i;
            while (descriptor[i] == '[')
            {
                i++;
            }
            if (descriptor[i] == 'L')
            {
                for (i++; descriptor[i] != ';'; i++) ;
            }
            i++;
            return descriptor.Substring(startI, i - startI);
        }

        public static MethodInfo ResolveMethod(string className, string methodName, string methodDescriptor)
        {
            ClassFile cFile = ClassFileManager.GetClassFile(className);

            MethodInfo method = null;
            bool foundMethod = false;
            //Search for method in cFile's method dictionary. If it's not there, repeat search in cFile's super and so on
            while (!foundMethod)
            {
                if(cFile==null)
                {
                    return method;

                }
                else
                {
                    if (cFile.MethodDictionary == null)
                    {
                        return method;

                    }
                }
                foundMethod = cFile.MethodDictionary.TryGetValue((methodName, methodDescriptor), out method);
                if (!foundMethod && className == "java/lang/invoke/MethodHandle")
                {
                    //Signature polymorphic method
                    if (methodName == "invoke")
                    {
                        method = cFile.MethodDictionary[("invoke", "([Ljava/lang/Object;)Ljava/lang/Object;")];
                        foundMethod = true;
                    }
                    else if (methodName == "invokeExact")
                    {
                        method = cFile.MethodDictionary[("invokeExact", "([Ljava/lang/Object;)Ljava/lang/Object;")];
                        foundMethod = true;
                    }
                }
                cFile = cFile.SuperClass;
            }
            return method;
        }

        public static string MakeDescriptor(HeapObject returnType, HeapArray parameterTypes)
        {
            int[] paramTypesArr = MemoryMarshal.Cast<byte, int>(parameterTypes.GetDataSpan()).ToArray();

            string descriptor = "(";
            for (int i = 0; i < paramTypesArr.Length; i++)
            {
                //throw new NotImplementedException();
                string typeName = ClassObjectName(paramTypesArr[i]);
                descriptor += typeName;
                if (typeName[0] == 'L')
                    descriptor += ";";
            }
            descriptor += ")";
            string returnTypeName = ClassObjectName(returnType).Replace('.','/');
            
            //if (IsPrimitiveType(returnTypeName))
            //{
            //    throw new NotImplementedException();
            //}

            //int j;
            //for (j = 0; returnTypeName[j] == '['; j++) ; //Skip through array brackets
            //if (j != returnTypeName.Length) //Object
            //{
            //    //Format as descriptor style
            //    returnTypeName = returnTypeName.Substring(0, j) + ")L" + returnTypeName.Substring(j, returnTypeName.Length - j).Replace(".", "/") + ";";
            //}
            //else
            //{
            //    descriptor += ")";
            //}
            descriptor += returnTypeName;

            return descriptor;
        }

        internal static Attributes.BootstrapMethod ResolveCallSiteSpecifier(CInvokeDynamicInfo callSiteSpecifier)
        {
            return callSiteSpecifier.BootstrapMethod;
        }
    }
}
